package com.york.tinyrpc.protoss.protocol;

import com.york.tinyrpc.protoss.constant.TinyRpcProtocolConstants;
import com.york.tinyrpc.protoss.enums.TinyRpcProtocolMessageSerializationEnum;
import com.york.tinyrpc.protoss.enums.TinyRpcProtocolMessageTypeEnum;
import com.york.tinyrpc.protoss.model.TinyRpcRequest;
import com.york.tinyrpc.protoss.model.TinyRpcResponse;
import com.york.tinyrpc.protoss.serializer.Serializer;
import com.york.tinyrpc.protoss.serializer.SerializerFactory;
import io.vertx.core.buffer.Buffer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.IOException;

/**
 * 自拟RPC协议-消息结构
 * 允许将消息体设置为泛型
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TinyRpcProtocolMessage<T> {

    /**
     * 请求头
     */
    private TinyRpcProtocolMessageHeader header;

    /**
     * 请求体
     * 根据header中的类型，决定是requestBody还是responseBody
     */
    private T body;

    /**
     * 请求头
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public static class TinyRpcProtocolMessageHeader {

        /**
         * magic【1 byte】：安全证书（也像JVM对编译得到的.class字节码中的魔数
         * 默认魔数0x1
         */
        private byte magic = TinyRpcProtocolConstants.PROTOCOL_MAGIC;

        /**
         * version【1 byte】：版本号，保证请求和响应的一致性
         * 默认版本0x1
         */
        private byte version = TinyRpcProtocolConstants.PROTOCOL_VERSION;

        /**
         * serializationId【1 byte】：序列化方式
         * 默认方式jdk
         */
        private byte serializationId = (byte) TinyRpcProtocolMessageSerializationEnum.JDK_SERIALIZER.getValue();

        /**
         * type【Req/Res 1 byte】：区别请求或响应的类型
         */
        private byte type;

        /**
         * status【1 byte】：响应结果
         */
        private byte status;

        /**
         * TinyRPC Request ID【8 byte】：TCP请求唯一标识
         */
        private long requestId;

        /**
         * Body Length【4 byte】
         */
        private int bodyLength;

    }

    /**
     * 将自拟的请求头编码，注意我们自定义请求头的顺序，编码和解码需要统一
     *
     * @param message
     * @return
     */
    public static Buffer encode(TinyRpcProtocolMessage message) {

        TinyRpcProtocolMessageHeader header = message.header;

        if (null == header) {
            return Buffer.buffer();
        }

        // 将请求头的数据写入Buffer缓存，字节数组
        Buffer buffer = Buffer.buffer();
        buffer.appendByte(header.getMagic());
        buffer.appendByte(header.getVersion());
        buffer.appendByte(header.getSerializationId());
        buffer.appendByte(header.getType());
        buffer.appendByte(header.getStatus());
        // requestId是8个字节
        buffer.appendLong(header.getRequestId());

        // 隐式的类型转换，byte提升为int
        TinyRpcProtocolMessageSerializationEnum serializerEnum =
                TinyRpcProtocolMessageSerializationEnum.getEnumByValue(header.getSerializationId());

        if (null == serializerEnum) {
            throw new RuntimeException("序列化协议不存在");
        }

        // 获取到目标序列化器
        Serializer targetSerializer = SerializerFactory.getSerializer(serializerEnum.getSerializerName());
        byte[] serializedBodyBytes;
        try {
            serializedBodyBytes = targetSerializer.serialize(message.body);
        } catch (IOException e) {
            throw new RuntimeException("序列化 body 失败");
        }

        // 继续写入Buffer
        buffer.appendInt(serializedBodyBytes.length);
        buffer.appendBytes(serializedBodyBytes);
        return buffer;
    }

    /**
     * 解码
     *
     * @param buffer
     * @return
     * @throws IOException
     */

    public static TinyRpcProtocolMessage<?> decode(Buffer buffer) throws IOException {

        // 分别从指定位置读出 Buffer
        TinyRpcProtocolMessage.TinyRpcProtocolMessageHeader header = new TinyRpcProtocolMessage.TinyRpcProtocolMessageHeader();

        // 校验魔数
        byte magic = buffer.getByte(0);
        if (magic != TinyRpcProtocolConstants.PROTOCOL_MAGIC) {
            throw new RuntimeException("消息 magic 非法");
        }
        header.setMagic(magic);

        // 校验版本
        byte version = buffer.getByte(1);
        if (version != TinyRpcProtocolConstants.PROTOCOL_VERSION) {
            throw new RuntimeException("消息 version 不一致");
        }
        header.setVersion(buffer.getByte(1));

        // 指定序列化器
        header.setSerializationId(buffer.getByte(2));

        header.setType(buffer.getByte(3));
        header.setStatus(buffer.getByte(4));

        // requestId8个字节
        header.setRequestId(buffer.getLong(5));

        // bodyLength4个字节
        header.setBodyLength(buffer.getInt(13));

        // 解决粘包问题，只读指定长度的数据,读取的是body
        // 17, 17 + header.getBodyLength() 即实际的body长度
        byte[] bodyBytes = buffer.getBytes(17, 17 + header.getBodyLength());

        // 解析消息体
        TinyRpcProtocolMessageSerializationEnum serializerEnum =
                TinyRpcProtocolMessageSerializationEnum.getEnumByValue(header.getSerializationId());
        if (serializerEnum == null) {
            throw new RuntimeException("序列化消息的协议不存在");
        }

        // 序列化
        Serializer targetSerializer = SerializerFactory.getSerializer(serializerEnum.getSerializerName());
        // 踩坑记录：对消息结构的设置有内存大小的限制，然而在设置常量值时却忽略了这一点，因此在编码和解码时得到错误的数值
        TinyRpcProtocolMessageTypeEnum messageTypeEnum = TinyRpcProtocolMessageTypeEnum.getEnumByType(header.getType());
        if (messageTypeEnum == null) {
            throw new RuntimeException("序列化消息的类型不存在");
        }

        switch (messageTypeEnum) {
            case REQUEST:
                TinyRpcRequest request = targetSerializer.deserialize(bodyBytes, TinyRpcRequest.class);
                return new TinyRpcProtocolMessage<>(header, request);
            case RESPONSE:
                TinyRpcResponse response = targetSerializer.deserialize(bodyBytes, TinyRpcResponse.class);
                return new TinyRpcProtocolMessage<>(header, response);
            case HEART_BEAT:
            case OTHERS:
            default:
//                todo 后续完善
                throw new RuntimeException("暂不支持该消息类型");
        }
    }

}
